package cn.zhxu.toys.concurrent;

import java.nio.charset.StandardCharsets;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.data.redis.connection.RedisConnection;
import org.springframework.data.redis.connection.ReturnType;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.serializer.RedisSerializer;

/**
 * Redis并发同步锁
 * @author Troy
 */
public class RedisTemplateSyncLock extends AbstractSyncLock {

	static Logger log = LoggerFactory.getLogger(RedisTemplateSyncLock.class);

	private StringRedisTemplate redisTemplate;

	/**
	 * 上锁
	 * @param key Redis Key
	 * @param requestId Redis Value
	 * @param maxHoldSeconds Redis Expire Time
	 * @return 是否上锁成功
	 */
	@Override
	@SuppressWarnings("all")
	protected boolean doTryLock(String key, String requestId, int maxHoldSeconds) {
		return Boolean.TRUE.equals(redisTemplate.execute((RedisConnection connection) -> {
			RedisSerializer<Object> keySerializer = (RedisSerializer<Object>) redisTemplate.getKeySerializer();
			RedisSerializer<Object> valueSerializer = (RedisSerializer<Object>) redisTemplate.getValueSerializer();
			/*
			  nxxx:
			  		NX: not exists, 只有key 不存在时才把key value set 到redis
			  		XX: is exists ，只有 key 存在是，才把key value set 到redis
			  expx:
			  		EX ： seconds 秒
			  		PX : milliseconds 毫秒
			  上锁与过期时间设置必须具有原子性，否则可能造成死锁（过期时间设置失败时）
			 */
			Object result = connection.execute("set", keySerializer.serialize(key), valueSerializer.serialize(requestId),
					"NX".getBytes(StandardCharsets.UTF_8), "EX".getBytes(StandardCharsets.UTF_8),
					String.valueOf(maxHoldSeconds).getBytes(StandardCharsets.UTF_8));
			if (result != null) {
				String res;
				if (result instanceof byte[]) {
					res = new String((byte[]) result);
				} else {
					res = result.toString();
				}
				log.debug("GET_LOCK[" + key + "]: " + res);
				return res.toUpperCase().contains("OK");
			} else {
				log.debug("GET_LOCK[" + key + "]: null");
			}
			return false;
		}));
	}

	@Override
	protected void doUnLock(String key, String requestId) {
		redisTemplate.execute((RedisConnection connection) -> {
			String script = "if redis.call('get', KEYS[1]) == ARGV[1] then redis.call('del', KEYS[1]) end";
			return connection.eval(script.getBytes(StandardCharsets.UTF_8), ReturnType.VALUE, 1, 
					key.getBytes(StandardCharsets.UTF_8), 
					requestId.getBytes(StandardCharsets.UTF_8));
		});
		log.debug("DEL_LOCK: " + key);
	}

	public StringRedisTemplate getRedisTemplate() {
		return redisTemplate;
	}

	public void setRedisTemplate(StringRedisTemplate redisTemplate) {
		this.redisTemplate = redisTemplate;
	}

}
